#!/usr/bin/python
# -*- coding:UTF-8 -*-
#第八章 构建价格模型

from random import random, randint
import math



#==================构造商品的价格=========================
# 构成单品价格
def wineprice(rating, age):
    peak_age = rating - 50

    # 根据等级来计算价格
    price = rating / 2
    if age > peak_age:
        # 经过“峰值年”，后继5年里其品质将会变差
        price = price * (5 - (age - peak_age)/2)
    else:
        # 价格在接近“峰值年”时会增加到原值的5倍
        price = price * (5 * ((age + 1) / peak_age))
    if price < 0: price = 0
    return price


# 构造葡萄酒价格的数据集（300个)
def wineset1():
    rows = []
    for i in range(300):
        # 随机生成年代和等级
        rating = random() * 50 + 50
        age = random() * 50

        # 得到一个参考价格
        price = wineprice(rating, age)

        # 增加“噪声”
        price *= (random() * 0.4 + 0.8)

        # 加入数据集
        rows.append({'input': (rating, age), 'result': price})

    return rows


# 对于葡萄酒定价问题而言，最简单的做法与人们尝试手工进行定价时所采用的做法是一样的，
# 算法通过寻找与当前所关注的商品情况相似的一组商品，对这些商品的价格求均值，进而作
# 出价格预测k-nearest neighbors(K近邻)

# 欧几里德相似度
def euclidean(v1, v2):
    d = 0.0
    for i in range(len(v1)):
        d += (v1[i] - v2[i]) ** 2
    return math.sqrt(d)


# 计算给定商品与原数据集中任一其他商品间的距离
def getdistance(data, vec1):
    distancelist = []
    for i in range(len(data)):
        vec2 = data[i]['input']
        distancelist.append((euclidean(vec1, vec2), i))
    distancelist.sort()
    return distancelist


# 对欧几里德的前k项结果求出了平均值
def knnestimate(data, vec1, k=5):
    # 得到经过排序的距离值
    dlist = getdistance(data, vec1)
    avg = 0.0

    # 对前k项结果求平均
    for i in range(k):
        idx = dlist[i][1]
        avg += data[idx]['result']
    avg = avg / k
    return avg

#=====================为近邻分配权重=====================================
# 由于上述算法有可能会选择距离太远的近邻，我们增加一些补偿的方法
# 1、根据距离的远近为其赋以相应的权重
# 商品越是相近,彼此间的距离就越小,我们须要一种方法来将距离转换为权重
# 下面是常见的三种方法：1、反函数法，将距离转换为权重所使用的是一个反函数
#											 2、减法函数
#                      3、高斯函数

# 反函数法
def inverseweight(dist, num=1.0, const=0.1):
    return num / (dist + const)

# 减法函数
def subtractweight(dist, const=1.0):
    if dist > const:
        return 0
    else:
        return const - dist

# 高斯函数
def gaussian(dist, sigma=10.0):
    return math.e ** (-dist ** 2 / (2 * sigma ** 2))

#===================对KNN进行权重处理============================
# 加权KNN
def weightedknn(data, vec1, k=5, weightf=gaussian):
    # 得到距离值
    dlist = getdistance(data, vec1)
    avg = 0.0
    totalweight = 0.0

    # 得到加权平均值
    for i in range(k):
        dist = dlist[i][0]
        idx = dlist[i][1]
        weight = weightf(dist)
        avg += weight * data[idx]['result']
        totalweight += weight
    avg = avg / totalweight
    return avg

# 交叉验证,数据拆分
def dividedata(data, test=0.5):
    trainset = []
    testset = []
    for row in data:
        if random() < test:
            testset.append(row)
        else:
            trainset.append(row)
    return trainset, testset

# 算法测试
def testalgorithm(algf, trainset, testset):
    error = 0.0
    for row in testset:
        guess = algf(trainset, row['input'])
        error += (row['result'] - guess) ** 2
    return error / len(testset)

def crossvalidate(algf, data, trials=100, test=0.05):
    error = 0.0
    for i in range(trials):
        trainset, testset = dividedata(data, test)
        error += testalgorithm(algf, trainset, testset)
    return error / trials


# ==========================第二段=============================
def wineset2():
    rows = []
    for i in range(300):
        rating = random() * 50 + 50
        age = random() * 50
        aisle = float(randint(1, 20))
        bottlesize = [375.0, 750.0, 1500.0, 3000.0][randint(0, 3)]
        price = wineprice(rating, age)
        rows.append({'input': (rating, age, aisle, bottlesize), 'result': price})
    return rows


# 数据缩放
def rescale(data, scale):
    scaleddata = []
    for row in data:
        scaled = [scale[i] * row['input'][i] for i in range(len(scale))]
        scaleddata.append({'input': scaled, 'result': row['result']})
    return scaleddata


def createcostfunction(algf, data):
    def costf(scale):
        sdata = rescale(data, scale)
        return crossvalidate(algf, sdata, trials=10)
    return costf

# ===========第三阶段:不对称分布===================
def wineset3():
    rows = wineset1()
    for row in rows:
        if random() < 0.5:
            # 葡萄酒是从折扣店购得
            row['result'] *= 0.5
    return rows

def probguess(data, vec1, low, high, k=5, weightf=gaussian):
    dlist = getdistance(data, vec1)
    nweight = 0.0
    tweight = 0.0

    for i in range(k):
        dist = dlist[i][0]
        idx = dlist[i][1]
        weight = weightf(dist)
        v = data[idx]['result']

        # 当前数据点位于指定范围内吗？
        if v >= low and v <= high:
            nweight += weight
        tweight += weight
    if tweight == 0: return 0

    # 概率等于位于指定范围内的权重值除以所有权重值
    return nweight / tweight

from pylab import *

def cumulativegraph(data, vec1, high, k=5, weightf=gaussian):
    t1 = arange(0.0, high, 0.1)
    cprob = array([probguess(data, vec1, 0, v, k, weightf) for v in t1])
    plot(t1, cprob)
    show()


def probabilitygraph(data, vec1, high, k=5, weightf=gaussian, ss=5.0):
    # 建立一个代表价格的值域范围
    t1 = arange(0.0, high, 0.1)

    # 得到整个值域范围内的所有概率
    probs = [probguess(data, vec1, v, v + 0.1, k, weightf) for v in t1]
    # 通过加上近邻概率的高斯计算结果,对概率仠做平滑处理
    smoothed = []
    for i in range(len(probs)):
        sv = 0.0
        for j in range(0, len(probs)):
            dist = abs(i - j) * 0.1
            weight = gaussian(dist, sigma=ss)
            sv += weight * probs[j]
        smoothed.append(sv)
    smoothed = array(smoothed)

    plot(t1, smoothed)
    show()